package Segmentacao;

import Estruturas.WatershedFila;
import javax.media.jai.PlanarImage;
import Controle.ManipularImagem;
import Estruturas.PixelWatershed;
import Matriz.ManipulacaoMatriz;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

public class Inundacao {


    public static PlanarImage aplicarWatershed(PlanarImage imagemOriginal) {

        int height = imagemOriginal.getHeight();
        int width = imagemOriginal.getWidth();
        int[][] matrizPixelsOriginal = ManipularImagem.planarToMatriz(imagemOriginal);

        int bacia = 0;
        int distanciaAtual = 0;

        WatershedFila fila = new WatershedFila();

        //1º Passo
       Vector<LinkedList<PixelWatershed>> vetorPixelsOrdenado = ManipulacaoMatriz.matrizToVetorOrdenado(matrizPixelsOriginal, height, width);
      
 
        //2º Passo
        for (int i = 0; i < vetorPixelsOrdenado.size(); i++) {// Imersão em cada nível de cinza
            LinkedList<PixelWatershed> pixelsNesseNivel = vetorPixelsOrdenado.get(i);//lista dos pixels nesse nivel

            for (int j = 0; j < pixelsNesseNivel.size(); j++) {//percorre todos os pixel do nivel
                PixelWatershed pixelAtual = pixelsNesseNivel.get(j);//pixel atual
                pixelAtual.setRotuloMASCARA();// Rotula como não processado os pixels neste nivel
                LinkedList<PixelWatershed> vizinhos = pixelAtual.getVizinhos();
                if(verificaVizinho(vizinhos, pixelAtual, fila)){
                     pixelAtual.setDistancia(1); // Inicializa a distância para o pixel ...
                     fila.AdicionarPixel(pixelAtual); // ... e usa a fila para processá-lo
                }
            }

            distanciaAtual = 1;
            fila.FilaAdicionarPixelFicticio();

            //3º Passo
            while (true) {
                PixelWatershed pixelAtual = fila.RemoverFila();
                if (pixelAtual.isFicticio()) { // Processou todos os pixels deste nível de cinza a esta distância
                    if (fila.FilaVazia())// Verifica a condição de parada
                    {
                        break;
                    }
                    else {// Reinsere a condição de parada
                        fila.FilaAdicionarPixelFicticio();
                        distanciaAtual++; // Após processar todos os pixels vizinhos imediatos de regiões já rotuladas, aumenta a distância geodésica
                        pixelAtual = fila.RemoverFila();
                    }
                }
                LinkedList<PixelWatershed> vizinhos = pixelAtual.getVizinhos();
                for (int j = 0; j < vizinhos.size(); j++) {
                    PixelWatershed vizinhoAtual = vizinhos.get(j);
                    /**
                    Verifica se o vizinho está rotulado e se a distância geodésica é
                    menor que a atual (mais próximo da descida do plateau, processado
                    previamente)
                     */
                    if ((vizinhoAtual.getDistancia() < distanciaAtual) && (vizinhoAtual.getRotulo() > 0 || vizinhoAtual.isRotuloWATERSHED())) {
                        if (vizinhoAtual.getRotulo() > 0) {
                            if (pixelAtual.isRotuloMASCARA() || pixelAtual.isRotuloWATERSHED()) { // Se o pixel ainda não foi processado ou pertence a watershed, atribui o label do mínimo, isto afina a linha de watershed
                                pixelAtual.setRotulo(vizinhoAtual.getRotulo());
                              //  vizinhoAtual.setRotulo(pixelAtual.getRotulo());
                            } else if (pixelAtual.getRotulo() != vizinhoAtual.getRotulo()) { // Se os rótulos forem diferentes (dois mínimos atingiram um ponto) o pixel é de watershed
                                pixelAtual.setRotuloWATERSHED();
                            }
                        } else if (pixelAtual.isRotuloMASCARA()) {
                            pixelAtual.setRotuloWATERSHED(); // Propagação do rótulo watershed

                        }
                    } else if (vizinhoAtual.isRotuloMASCARA() && vizinhoAtual.getDistancia() == 0) { // Propaga o processamento dentro de um plateau
                        vizinhoAtual.setDistancia(distanciaAtual + 1);
                        fila.AdicionarPixel(vizinhoAtual);
                    }
                }
            }
            //4º passo
           
            for (int j = 0; j < pixelsNesseNivel.size(); j++)
            {// Novos mínimos não são enfileirados, são processados à parte
                PixelWatershed pixelAtual = pixelsNesseNivel.get(j);//pixel atual
                pixelAtual.setDistancia(0);
                if (pixelAtual.isRotuloMASCARA()) {
                    bacia++;
                    fila.AdicionarPixel(pixelAtual);
                    pixelAtual.setRotulo(bacia); // Propaga o label para os pixels vizinhos não processados usando a fila
                    while (!fila.FilaVazia()) {
                        pixelAtual = fila.RemoverFila();
                        LinkedList<PixelWatershed> vizinhos = pixelAtual.getVizinhos();
                        for (int k = 0; k < vizinhos.size(); k++) {
                            PixelWatershed vizinhoAtual = vizinhos.get(k);
                            if (vizinhoAtual.isRotuloMASCARA()) {
                                fila.AdicionarPixel(vizinhoAtual);
                                vizinhoAtual.setRotulo(bacia);
                            }
                        }
                    }
                }
            }


            //imprimeRotulos(vetorPixelsOrdenado, height, width, imagemOriginal);
        }
        System.out.println("Quantidade de bacias - " + bacia);
        engrossarWater(vetorPixelsOrdenado, height, width);
        return resultadoInundacao(vetorPixelsOrdenado, height, width, imagemOriginal);
    }

    public static PlanarImage aplicarWatershedComMinimo(PlanarImage imagemOriginal) {

        int height = imagemOriginal.getHeight();
        int width = imagemOriginal.getWidth();
        int[][] matrizPixelsOriginal = ManipularImagem.planarToMatriz(imagemOriginal);

        int bacia = 0;
        int distanciaAtual = 0;

        WatershedFila fila = new WatershedFila();

        //1º Passo
       Vector<LinkedList<PixelWatershed>> vetorPixelsOrdenado = ManipulacaoMatriz.matrizToVetorOrdenado(matrizPixelsOriginal, height, width);
       vetorPixelsOrdenado.get(0).get(5).SetMinimo();

        //2º Passo
        for (int i = 0; i < vetorPixelsOrdenado.size(); i++) {// Imersão em cada nível de cinza
            LinkedList<PixelWatershed> pixelsNesseNivel = vetorPixelsOrdenado.get(i);//lista dos pixels nesse nivel

            for (int j = 0; j < pixelsNesseNivel.size(); j++) {//percorre todos os pixel do nivel
                PixelWatershed pixelAtual = pixelsNesseNivel.get(j);//pixel atual
                pixelAtual.setRotuloMASCARA();// Rotula como não processado os pixels neste nivel
                LinkedList<PixelWatershed> vizinhos = pixelAtual.getVizinhos();
                if(verificaVizinho(vizinhos, pixelAtual, fila)){
                     pixelAtual.setDistancia(1); // Inicializa a distância para o pixel ...
                     fila.AdicionarPixel(pixelAtual); // ... e usa a fila para processá-lo
                }
            }

            distanciaAtual = 1;
            fila.FilaAdicionarPixelFicticio();

            //3º Passo
            while (true) {
                PixelWatershed pixelAtual = fila.RemoverFila();
                if (pixelAtual.isFicticio()) { // Processou todos os pixels deste nível de cinza a esta distância
                    if (fila.FilaVazia())// Verifica a condição de parada
                    {
                        break;
                    }
                    else {// Reinsere a condição de parada
                        fila.FilaAdicionarPixelFicticio();
                        distanciaAtual++; // Após processar todos os pixels vizinhos imediatos de regiões já rotuladas, aumenta a distância geodésica
                        pixelAtual = fila.RemoverFila();
                    }
                }
                LinkedList<PixelWatershed> vizinhos = pixelAtual.getVizinhos();
                for (int j = 0; j < vizinhos.size(); j++) {
                    PixelWatershed vizinhoAtual = vizinhos.get(j);
                    /**
                    Verifica se o vizinho está rotulado e se a distância geodésica é
                    menor que a atual (mais próximo da descida do plateau, processado
                    previamente)
                     */
                    if ((vizinhoAtual.getDistancia() < distanciaAtual) && (vizinhoAtual.getRotulo() > 0 || vizinhoAtual.isRotuloWATERSHED())) {
                        if (vizinhoAtual.getRotulo() > 0) {
                            if (pixelAtual.isRotuloMASCARA() || pixelAtual.isRotuloWATERSHED()) { // Se o pixel ainda não foi processado ou pertence a watershed, atribui o label do mínimo, isto afina a linha de watershed
                                pixelAtual.setRotulo(vizinhoAtual.getRotulo());
                              //  vizinhoAtual.setRotulo(pixelAtual.getRotulo());
                            } else if (pixelAtual.getRotulo() != vizinhoAtual.getRotulo()) { // Se os rótulos forem diferentes (dois mínimos atingiram um ponto) o pixel é de watershed
                                pixelAtual.setRotuloWATERSHED();
                            }
                        } else if (pixelAtual.isRotuloMASCARA()) {
                            pixelAtual.setRotuloWATERSHED(); // Propagação do rótulo watershed

                        }
                    } else if (vizinhoAtual.isRotuloMASCARA() && vizinhoAtual.getDistancia() == 0) { // Propaga o processamento dentro de um plateau
                        vizinhoAtual.setDistancia(distanciaAtual + 1);
                        fila.AdicionarPixel(vizinhoAtual);
                    }
                }
            }
            //4º passo

            for (int j = 0; j < pixelsNesseNivel.size(); j++)
            {// Novos mínimos não são enfileirados, são processados à parte

                PixelWatershed pixelAtual = pixelsNesseNivel.get(j);//pixel atual
                
                    pixelAtual.setDistancia(0);
                    if (pixelAtual.isRotuloMASCARA()) {
                        bacia++;
                        fila.AdicionarPixel(pixelAtual);
                        pixelAtual.setRotulo(bacia); // Propaga o label para os pixels vizinhos não processados usando a fila
                        while (!fila.FilaVazia()) {
                            pixelAtual = fila.RemoverFila();
                            LinkedList<PixelWatershed> vizinhos = pixelAtual.getVizinhos();
                            for (int k = 0; k < vizinhos.size(); k++) {
                                PixelWatershed vizinhoAtual = vizinhos.get(k);
                                if (vizinhoAtual.isRotuloMASCARA()) {
                                    fila.AdicionarPixel(vizinhoAtual);
                                    vizinhoAtual.setRotulo(bacia);
                                }
                            }
                        }
                    }
                
            }


            //imprimeRotulos(vetorPixelsOrdenado, height, width, imagemOriginal);
        }
        System.out.println("Quantidade de bacias - " + bacia);
        engrossarWater(vetorPixelsOrdenado, height, width);
        int[] bacias = new int[4];
        bacias[0]=4;
        bacias[1]=6;
        bacias[2]=7;
        bacias[3]=5;
        selecionarBacias(vetorPixelsOrdenado, bacias);
        return resultadoSelecaoInundacao(vetorPixelsOrdenado, height, width, imagemOriginal);
        //return resultadoInundacao(vetorPixelsOrdenado, height, width, imagemOriginal);
    }

    public static PlanarImage resultadoInundacao(Vector<LinkedList<PixelWatershed>> vetor, int height, int width, PlanarImage source) {
        int[][] resultado = ManipulacaoMatriz.preencherMatriz(height, width, 255);

        for (int i = 0; i < vetor.size(); i++) {
            for (int j = 0; j < vetor.get(i).size(); j++) {
                PixelWatershed pixel = vetor.get(i).get(j);
                if (pixel.isRotuloWATERSHED()) {
                    resultado[pixel.getX()][pixel.getY()] = 0;
                } else {
                    resultado[pixel.getX()][pixel.getY()] = 255;
                }
            }
        }
       // ManipulacaoMatriz.imprimeMatriz(resultado, height, width);
        return ManipularImagem.matrizToPlanar(resultado, height, width, source);
    }

    public static PlanarImage imprimeRotulos(Vector<LinkedList<PixelWatershed>> vetor, int height, int width, PlanarImage source) {
        int[][] resultado = ManipulacaoMatriz.preencherMatriz(height, width, 255);

        for (int i = 0; i < vetor.size(); i++) {
            for (int j = 0; j < vetor.get(i).size(); j++) {
                PixelWatershed pixel = vetor.get(i).get(j);
                    resultado[pixel.getX()][pixel.getY()] =pixel.getRotulo();
            }
        }
      //  ManipulacaoMatriz.imprimeMatriz(resultado, height, width);
        return ManipularImagem.matrizToPlanar(resultado, height, width, source);
    }

    public static  boolean verificaVizinho(LinkedList<PixelWatershed> vizinhos, PixelWatershed pixelAtual, WatershedFila fila){
        for (int k = 0; k < vizinhos.size(); k++) {
                    PixelWatershed vizinhoAtual = vizinhos.get(k);
                    if (vizinhoAtual.isRotuloWATERSHED() || vizinhoAtual.getRotulo() > 0) {
                       return true;
                    }
        }
        return false;
    }

    public static void imprimirVizinhos(LinkedList<PixelWatershed> vizinhos){
        for (int i = 0; i < vizinhos.size(); i++) {
           // System.out.println(vizinhos.get(i).getCoordenadas());
        }
    }

    public static void engrossarWater(Vector<LinkedList<PixelWatershed>> vetor, int height, int width) {
        for (int i = 0; i < vetor.size(); i++) {
            for (int j = 0; j < vetor.get(i).size(); j++) {
                PixelWatershed pixel = vetor.get(i).get(j);
                LinkedList<PixelWatershed> vizinhos = pixel.getVizinhos();
                for (int k = 0; k < vizinhos.size(); k++) {
                    PixelWatershed vizinho = vizinhos.get(k);
                    if (pixel.getRotulo() != vizinho.getRotulo() && !pixel.isRotuloWATERSHED() && !vizinho.isRotuloWATERSHED()) {
                        pixel.setRotuloWATERSHED();
                        vizinho.setRotuloWATERSHED();
                    }
                }
            }
        }
    }

    public static void selecionarBacias(Vector<LinkedList<PixelWatershed>> vetor, int[] bacias) {

        for (int l = 0; l < bacias.length; l++) {
            for (int i = 0; i < vetor.size(); i++) {
                for (int j = 0; j < vetor.get(i).size(); j++) {
                    PixelWatershed pixel = vetor.get(i).get(j);
                    LinkedList<PixelWatershed> vizinhos = pixel.getVizinhos();
                    for (int k = 0; k < vizinhos.size(); k++) {
                        PixelWatershed vizinho = vizinhos.get(k);
                        if (pixel.getRotulo() == bacias[l] && vizinho.isRotuloWATERSHED()) {
                            pixel.setNovaWater();
                            vizinho.setNovaWater();
                        }
                    }
                }
            }
        }
    }

    public static PlanarImage resultadoSelecaoInundacao(Vector<LinkedList<PixelWatershed>> vetor, int height, int width, PlanarImage source) {
        int[][] resultado = ManipulacaoMatriz.preencherMatriz(height, width, 255);

        for (int i = 0; i < vetor.size(); i++) {
            for (int j = 0; j < vetor.get(i).size(); j++) {
                PixelWatershed pixel = vetor.get(i).get(j);
                if (pixel.isNovaWater()) {
                    resultado[pixel.getX()][pixel.getY()] = 0;
                } else {
                    resultado[pixel.getX()][pixel.getY()] = 255;
                }
            }
        }
       // ManipulacaoMatriz.imprimeMatriz(resultado, height, width);
        return ManipularImagem.matrizToPlanar(resultado, height, width, source);
    }


    public static void main(String[] args) {
        Inundacao.aplicarWatershed(null);
    }

}
